"""

.. module:: runtime
.. moduleauthor:: @author: 

The purpose of this module is to control the node startup and runtime.
   
.. seealso:: 

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
from Queue import Queue
import time
import logging
import os
import urlparse
import win32serviceutil
import win32service
import win32event 
import winerror
import sys
import random
import datetime

from boot import Console
from datastore.database import DataStore
from datastore.resource import Resource

# Constants
#
SERVICE_NAME_PREFIX = "HAF eDA Node"
SERVICE_DISPLAYNAME_PREFIX = "HAF eDA Node"
SERVICE_DESCRIPTION = "HAF eDA Node for doing blah"

CONFIGSTATE_INITIALISED_STAGE1 = "1"
CONFIGSTATE_INITIALISED_STAGE2 = "2"

# Classes
#

class StartupException(Exception):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
    """
    
    # Class constants
    #
    def __init__(self):
        # <purpose>
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        pass


class NodeService(win32serviceutil.ServiceFramework):
    """The purpose of this class is to interface with Windows Services.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
    """
    
    _svc_name_ = "NodeService"
    _svc_display_name_ = SERVICE_DISPLAYNAME_PREFIX
    _svc_description_ = SERVICE_DESCRIPTION

    def __init__(self, args):
        # Initialise the NodeService class.
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        # Note: After the service is removed, the DLL containing the message 
        # definitions is unloaded and a more verbose message is seen in the Windows
        # Event Log.
        #
        self.logger = logging.getLogger('yarra.runtime.rt.NodeService')
        self.logger.log(logging.DEBUG, "")
        
        try:
            win32serviceutil.ServiceFramework.__init__(self, [NodeService._svc_name_])
            
            # Create an event which we will use to wait on. The "service stop" request will set this event.
            #
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        except Exception as e:
            self.logger.exception("%s" % e)
        
    def SvcStop(self):
        """This function is called by Windows Service to stop the node service. 
        
        :rtype: None
        """
        
        self.logger.log(logging.DEBUG, "")
        
        # Before we do anything, tell the SCM we are starting the stop process.
        #
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        
        # And set my event.
        #
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        """This function is called by Windows Service to run the node service. 
        
        :rtype: None
        """
        
        self.logger.log(logging.INFO, "")
        
        rt = Runtime()
        rt.start()
        
        self.timeout=60000
        while (True):
            # Wait for service stop signal, if I timeout, loop again
            #
            rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
            
            self.logger.log(logging.DEBUG, "Checking for STOP from Windows Service...")
            
            # Check to see if self.hWaitStop happened
            #
            if (rc == win32event.WAIT_OBJECT_0):
                # Stop signal encountered
                #
                self.logger.log(logging.DEBUG, "STOP event received by NodeService")
                
#                local_uri = rt.spaces_context.uri('local:')
                local_uri = rt.spaces_context.uri(Resource.BINDING_NS_NAME_LOCALHOST + ':')
                rt.spaces_context.put(local_uri.quit, Resource(), 'cb_node_shutdown')
                break
        
        rt.post_start()
        

    @staticmethod
    def InstallService(distribution):
        """This function installs the node with Windows Service. 
        
        :param distribution: Distribution - distribution configuration.
        :rtype: None
        """
        
        # Note: The current working directory (CWD) for Windows Services is:
        #
        #       - "C:\Windows\system32"
        #
        #       so if we look for the datastore in the CWD we won't find it. So, on
        #       startup, we infer the path to the datastore from the path of the 
        #       executable.
        #
        
        try:
            win32serviceutil.InstallService(pythonClassString=win32serviceutil.GetServiceClassString(NodeService), 
                                            serviceName=NodeService._svc_name_, 
                                            displayName=NodeService._svc_display_name_, 
                                            startType=win32service.SERVICE_AUTO_START, 
#                                            startType=win32service.SERVICE_DEMAND_START, 
                                            exeName=distribution.exe_pathname, 
                                            exeArgs=distribution.database_filename,
                                            description=NodeService._svc_description_)
            
            logging.log(logging.INFO, "Service (%s) installed." % NodeService._svc_name_)
        except win32service.error, (hr, fn, msg) :
            if (hr == winerror.ERROR_SERVICE_EXISTS):
                # Service already exists. Update it.
                #
                try:
                    logging.log(logging.INFO, "Service (%s) already installed. Updating." % NodeService._svc_name_)
                    
                    win32serviceutil.ChangeServiceConfig(pythonClassString=win32serviceutil.GetServiceClassString(NodeService), 
                                                         serviceName=NodeService._svc_name_, 
                                                         startType=win32service.SERVICE_AUTO_START, 
#                                                         startType=win32service.SERVICE_DEMAND_START, 
                                                         exeName=distribution.exe_pathname,
                                                         exeArgs=distribution.database_filename,
                                                         displayName=NodeService._svc_display_name_, 
                                                         description=NodeService._svc_description_)

                    logging.log(logging.INFO, "Service (%s) updated." % NodeService._svc_name_)
                except win32service.error, (hr, fn, msg):
                    logging.exception("Error changing service (%s) configuration: %s (%d)" % (NodeService._svc_name_, msg, hr))
                    raise NodeServiceException(msg)
            else:
                logging.exception("Error installing service (%s): %s (%d)" % (NodeService._svc_name_, msg, hr))
                raise NodeServiceException(msg)
    
    @staticmethod
    def RemoveService():
        """This function removes the node from Windows Service. 
        
        :rtype: None
        """
        
        try:
            win32serviceutil.RemoveService(serviceName=NodeService._svc_name_)
            logging.log(logging.INFO, "Service (%s) removed." % NodeService._svc_name_)
        except win32service.error, (hr, fn, msg) :
            logging.exception("Error removing service (%s): %s (%d)" % (NodeService._svc_name_, msg, hr))
            raise NodeServiceException(msg)


class NodeServiceException(Exception):
    """This class represents an exception during Windows Service installation
    or running.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
    """
    
    def __init__(self, msg):
        self.msg = msg
    
    def __str__(self):
        return self.msg


class Runtime(object):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
        
    distribution = None
    
    def __init__(self):
        # <purpose>
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        # list of all the tasks started by runtime
        #
        self.tasks = []
        self.spaces_context = None
        self.ds = None
        
        self.logger = logging.getLogger('yarra.runtime.rt.Runtime')
    
    def get_class_type(self, module_path, class_name):
        """This function returns the type of the class. 
        
        :param module_path: <class> or <type>
        :param class_name: <class> or <type>
        :rtype: <return type>
        """
        
        # import the module
        import_string = 'from {0} import *'.format(module_path)
        self.logger.log(logging.DEBUG, "executing import: {0} ".format(import_string))
        exec import_string
        # get reference to the module
        module = sys.modules[module_path]
        # get a reference to the class
        targetClass = getattr(module, class_name)
        return targetClass
    
    def initialise_spaces(self, config_file_rs_list=[], site_file_rs_list=[], template_file_rs_list=[]):
        """This function initialises spaces from configuration.
        
        The method initialises spaces with:
        - all the uri namespaces bindings in the argument configuration files
        - the configuration files. 
        - the site configurations.
        
        :param config_file_rs_list: List - list of resource config files.
        :param site_file_rs_list: List - list of resource site files.
        :param template_file_rs_list: List - list of resource HTML template files.
        """
        
        from datastore.resource import Resource
        
        # load all the bindings first
        #
        for config_file_rs in config_file_rs_list:
            bindings = config_file_rs.get_var(Resource.KEY_URI_PACKAGE_MANIFEST_BINDING)
            self.spaces_context.load_bindings(bindings)
        
        config_uri = self.spaces_context.uri(Resource.LOCALHOST_CONFIGURATION)
                
        # now initialise spaces with other configuration data using the bindings 
        # loaded in the code above to make it easier to navigate the configuration
        # files.
        #
        for config_file_rs in config_file_rs_list:
            # add the configuration file to spaces
            #
            package_name = config_file_rs.get_single_metadata(Resource.KEY_URI_PACKAGE_PACKAGENAME)
            project_name = config_file_rs.get_single_metadata(Resource.KEY_URI_PACKAGE_PROJECTNAME)
            config_uri = config_uri.__getattr__(project_name).__getattr__(package_name)
            self.logger.log(logging.DEBUG, "loading configuration.xml (%s) into spaces" % config_uri)
            self.spaces_context.put(config_uri, config_file_rs)
            
            # add the spaces defs to spaces
            #
            spaces_defs = config_file_rs.get_var(Resource.KEY_URI_PACKAGE_MANIFEST_SPACES)
            for spaces_def_key in spaces_defs.keys():
                spaces_def = spaces_defs[spaces_def_key]
                new_spaces_def_rs = Resource()
                new_spaces_def_rs.set_var(Resource.KEY_URI_PACKAGE_MANIFEST_SPACES, spaces_def)
                spaces_def_uri = self.spaces_context.uri(spaces_def_key)
                self.logger.log(logging.DEBUG, "loading definition (%s) into spaces" % spaces_def_uri)
                self.spaces_context.put(spaces_def_uri, new_spaces_def_rs)
        
        # load site definitions into spaces...
        #
        for site_file_rs in site_file_rs_list:
            site_file_uri = site_file_rs.get_single_metadata(Resource.KEY_URI_PACKAGE_SITEURI)
            self.logger.log(logging.DEBUG, "loading site (%s) into spaces" % site_file_uri)
            self.spaces_context.put(site_file_uri, site_file_rs)
        
        # load HTML templates into spaces...
        #
        for template_file_rs in template_file_rs_list:
            template_file_uri = template_file_rs.get_single_metadata(Resource.KEY_URI_PACKAGE_DATAURI)
            self.logger.log(logging.DEBUG, "loading HTML template (%s) into spaces" % template_file_uri)
            self.spaces_context.put(template_file_uri, template_file_rs)
        
    def create_task(self, ds, spaces, task_fully_qualified_path, task_uri, task_def_rs):
        """This function returns an instance of the task specified by the qualified path and task definition. 
        
        :param ds: datastore.database.DataStore
        :param spaces: runtime.spaces.Spaces
        :param task_fully_qualified_path: <class> or <type>
        :param task_uri: <class> or <type>
        :param task_def_rs: <class> or <type>
        :rtype: runtime.task.Task
        """
        
        self.logger.log(logging.DEBUG, "task_fully_qualified_path (%s), task_uri (%s), task_def_rs (%s)" % (task_fully_qualified_path, task_uri, task_def_rs.pretty_print()))
        
        from runtime.task import Task, NonTaskException
             
        module_path, task_class_name = task_fully_qualified_path.strip().rsplit('.', 1)
        target_class = self.get_class_type(module_path, task_class_name)
        
        # make sure the class is a task or a subclass of a task
        #
        if not (Task is target_class or Task in target_class.__bases__):
            raise NonTaskException()
        else:
            # instantiate the class
            #
            return target_class(ds, spaces, task_uri, task_def_rs)
    
    def create_tasks_from_configuration(self, config_file_rs):
        """This function returns a list of created tasks defined in the manifest. 
        
        :param config_file_rs: datastore.resource.Resource
        :rtype: List - list of runtime.task.Task
        """
        
#        self.logger.log(logging.DEBUG, "config_file_rs (%s)" % config_file_rs)
        self.logger.log(logging.DEBUG, "")
        
        from datastore.resource import Resource
        
#        package_uri = self.spaces_context.uri('package:')
        package_uri = self.spaces_context.uri(Resource.BINDING_NS_NAME_PACKAGE + ':')
#        tasks_uri = self.spaces_context.uri('tasks:')
        tasks_uri = self.spaces_context.uri(Resource.BINDING_NS_NAME_TASK + ':')
        tasks = []
        for task_def in config_file_rs.get_var(package_uri.manifest.tasks):
            self.logger.log(logging.DEBUG, "task_def (%s)" % task_def)
            
            # create a new resource that contains the task definition for a task
            # and put it to the task's uri
            #
            task_def_rs = Resource()
            task_def_rs.set_var(package_uri.manifest.tasks, task_def)
            task_def_rs.set_type(tasks_uri.type.taskdef)
            task_uri_str = task_def[str(package_uri.taskuri)]
            task_uri = self.spaces_context.uri(task_uri_str)
                            
            task_fully_qualified_path = task_def[str(package_uri.classname)]
            
            task = self.create_task(self.ds, self.spaces_context.spaces, task_fully_qualified_path, task_uri, task_def_rs)            
            tasks.append(task)
        
        return tasks
        
    def update_packages(self):
        """This function ... 
        
        :rtype: <return type>
        """
        
        from package.manager import PackageManager
        
        package_manager = PackageManager()
        
        from datastore.database import NotFoundException
        
#        uri_datastore = self.spaces_context.uri('datastore:')
        uri_datastore = self.spaces_context.uri(Resource.BINDING_NS_NAME_DATASTORE + ':')
#        uri_package = self.spaces_context.uri('package:')
        uri_package = self.spaces_context.uri(Resource.BINDING_NS_NAME_PACKAGE + ':')
        try:
            # get all the packages that need to be installed
            resources = self.ds.find([(uri_datastore.resource.type, uri_package.type.package),
                                      (uri_package.status, 'to_install')])
            
            for resource in resources:
                self.logger.log(logging.DEBUG, "Resource to update = %s" % resource.pretty_print())
                package_manager.update_package(resource, self.ds)
            self.logger.log(logging.INFO, "Update Complete")
            return True
        except NotFoundException:
            self.logger.exception('No packages in datastore to install.')
            return False
    
    def post_start(self):
        """This function handles post start events.
        
        Handles events after the node has successfully started. This includes shutting down of the node. 

        :rtype: None
        """
        
        self.logger.log(logging.DEBUG, "")
        
        while True:
            (callback, method, uri, return_value, result, self.spaces_context, kwargs) = self.spaces_context.queue.get(True)
            
            self.logger.log(logging.DEBUG, "Received queued message, callback (%s), method (%s), uri (%s)" % (callback, method, uri))
            
            # very simple right now. Checks the callback is the shutdown one. Otherwise ignores the callback
            if callback == 'cb_node_shutdown' and method == 'put':
                self.logger.log(logging.DEBUG, "Received cb_node_shutdown call.")
                
                from datastore.resource import Resource
                # return some confirmation to the user.
                # TODO: NOTE: This is misleading as the shutdown hasn't even started. We can actually only shutdown the 
                # tasks after we return the message. Otherwise if we try to shutdown all the tasks first, the http server
                # will be destroyed and we can't return anything to the user
                rs = Resource()
                rs.content = 'Shutdown'
                self.spaces_context.return_from_callback(self.spaces_context, rs)
                return self.cb_node_shutdown(uri)
            
    def cb_node_shutdown(self, uri):
        """This function shuts down the node.
        
        Shuts down the node currently by stopping all the tasks. Returns the exit code to specify a shutdown or a restart. 
        
        :param uri: datastore.uri.Uri - uri called to shutdown
        :rtype: int
        """
        
        # set exit code to shutdown or restart
        if uri.split2()[-1] == 'quit':
            # shutdown
            exit_code = 0
        else:
            # restart
            exit_code = 1
#        tasks_uri = self.spaces_context.uri('tasks:').collection(True)
        tasks_uri = self.spaces_context.uri(Resource.BINDING_NS_NAME_TASK + ':').collection(True)
        self.stop_tasks(tasks_uri)
        
        # don't exit until all the tasks have shutdown
        for task in self.tasks:
            while task.isAlive():
                # if a task is still alive, wait and check again
                time.sleep(1)
        
        return exit_code
    
    def stop_tasks(self, uri):
        """This function stops tasks.
        
        Stops the tasks registered under the uri. It does this by checking if the resource at that uri is a 
        task definition resource. If it is, then it will attempt to stop the task by deleting the resource at the
        uri. 
        
        :param uri: datastore.uri.Uri - uri of where all the tasks are located
        :rtype: None
        """
        
        self.logger.log(logging.INFO, "")
        
#        task_def_uri = self.spaces_context.uri('tasks:').type.taskdef
        task_def_uri = self.spaces_context.uri(Resource.BINDING_NS_NAME_TASK + ':').type.taskdef
        child_uris = self.spaces_context.find(uri)
        if child_uris:
            for child_uri in child_uris:
                rs = self.spaces_context.get(child_uri)
                if rs and rs.get_type() == task_def_uri:
                    # TODO: How do we stop a task? Delete is being used in HTTPTask to delete as site.
                    #
                    self.spaces_context.delete(child_uri)
                else:
                    self.stop_tasks(child_uri)
    
    def install(self, configurables, distribution):
        """This function the locks the datastore, generates the administration
        password, creates the Root node of the datastore, and writes all 
        configurables to the datastore, including the admin password. 
        
        :param configurables: Configurables - user configuration items.
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "")
        
        # open and lock the database.
        #
        ds = DataStore(os.path.join(distribution.database_pathname, distribution.database_filename))
        ds.lock()
        
        # generate administration password. We generate an 8-digit random number > 0.
        # Display this to administration user.
        #
        admin_password = `random.randrange(1, 10000000, 1)`
        Console.display("Administration password is (%s)" % admin_password)
        
        dt = datetime.datetime.now()
        
        # create the Root node and write to datastore...
        #
        rs = Resource()
        rs.content = "Root"
        rs.set_metadata(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.LOCALHOST_ROOT)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_ROOT_NODEID, distribution.node_ID)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_ROOT_TIMESTAMP, dt.strftime("%d/%m/%Y %H:%M:%S"))
        ds.put(rs)

        # create the configuration resource
        #
        rs = Resource()
        rs.content = "Configuration"
        rs.set_metadata(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.LOCALHOST_CONFIGURATION)
        if (type(configurables.proxy_url) is urlparse.ParseResult):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_URL, urlparse.urlunparse(configurables.proxy_url))
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_URL, None)
        if (len(configurables.proxy_user) > 0):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_USER, configurables.proxy_user)
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_USER, None)
        if (len(configurables.proxy_password) > 0):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_PASSWORD, configurables.proxy_password)
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_PROXY_PASSWORD, None)
        if (type(configurables.firewall_download_site_url) is urlparse.ParseResult):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_FIREWALL_DOWNLOADSITEURL, urlparse.urlunparse(configurables.firewall_download_site_url))
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_FIREWALL_DOWNLOADSITEURL, None)
        if (type(configurables.firewall_admin_site_url) is urlparse.ParseResult):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_FIREWALL_ADMINSITEURL, urlparse.urlunparse(configurables.firewall_admin_site_url))
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_FIREWALL_ADMINSITEURL, None)
        if (type(configurables.distribution_datastore_url) is urlparse.ParseResult):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_DISTRIBUTION_DATASTOREURL, urlparse.urlunparse(configurables.distribution_datastore_url))
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_DISTRIBUTION_DATASTOREURL, None)
        if (len(configurables.admin_site_url) > 0):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_ADMIN_SITEURL, configurables.admin_site_url)
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_ADMIN_SITEURL, None)
        if (len(configurables.node_ip_address) > 0):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_IPADDRESS, configurables.node_ip_address)
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_IPADDRESS, None)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_PORTRANGEHIGH, configurables.node_port_range_high)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_PORTRANGELOW, configurables.node_port_range_low)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_IS_SERVICE, configurables.is_service)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_USING_PROXY, configurables.using_proxy)
        if (len(admin_password) > 0):
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_ADMIN_PASSWORD, admin_password)
        else:
            rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_ADMIN_PASSWORD, None)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_CONFIG_STATE, CONFIGSTATE_INITIALISED_STAGE1)
        ds.put(rs)
    
    def start(self):
        """This function is the mainline of Runtime regardless of whether we're
        started as a service or from a shell.
        """
        
        from datastore.database import DataStore
        from runtime.spaces import Spaces, SpacesContext
        
        self.logger.log(logging.INFO, "")

        queue = Queue()
        spaces = Spaces()
        self.spaces_context = SpacesContext(spaces, queue)
        
        # open and lock the database.
        #
        self.ds = DataStore(os.path.join(Runtime.distribution.database_pathname, Runtime.distribution.database_filename))
        self.ds.lock()
        
        config_file_rs_list = self.ds.find([(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.KEY_URI_PACKAGE_TYPE_CONFIGURATION)])
        site_file_rs_list = self.ds.find([(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.KEY_URI_PACKAGE_TYPE_SITE)])
        template_file_rs_list = self.ds.find([(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.KEY_URI_SERVICES_TYPE_TRANSFORM_TEMPLATE)])
        self.initialise_spaces(config_file_rs_list, site_file_rs_list, template_file_rs_list)
        
        # Watch "local:quit" Uri on Spaces. This is our shutdown event.
        #
#        local_uri = self.spaces_context.uri('local:')
        local_uri = self.spaces_context.uri(Resource.BINDING_NS_NAME_LOCALHOST + ':')
        self.spaces_context.watch(['get'], local_uri.quit, 'cb_node_shutdown')
        
        # update packages
        is_updated = self.update_packages()
        if (is_updated):
            self.logger.log(logging.INFO, 'Restart')
            sys.exit(1)
        
        # create all tasks of packages that are active
        for config_file_rs in config_file_rs_list:
            self.tasks.extend(self.create_tasks_from_configuration(config_file_rs))
        
        # start all the tasks
        for task in self.tasks:
            task.start()
        
        self.logger.log(logging.INFO, 'All tasks have started')
    
    def startProcess(self, distribution):
        """This function is starts Runtime for a command-line start.
        
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "")
        
        Runtime.distribution = distribution
        self.start()
        
        # TODO: Tie this shutdown request to Ctrl-C
        #
#        rt.spaces_context.process(Uri('http://blah/quit'), get='cb_node_shutdown')

        self.post_start()
    
    def startAsService(self, distribution):
        """This function starts Runtime as a Service.
        
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "")
        
        Runtime.distribution = distribution
        
        # We have been started from Windows Services...
        #
        import servicemanager
        servicemanager.PrepareToHostSingle(NodeService)
    
        try:
            servicemanager.StartServiceCtrlDispatcher()
        except:
            self.logger.exception("Unable to start as service: %s" % sys.exc_info()[1])
    
    def installService(self, distribution):
        """This function ... 
        
        :param distribution: Distribution
        :param param_2: <class> or <type>
        :rtype: <return type>
        """
        
        self.logger.log(logging.INFO, "")
        
        try:
            NodeService.InstallService(distribution)
            self.logger.log(logging.INFO, "Service installed.")
        except Exception as e:
            self.logger.log(logging.ERROR, "Error installing service: %s" % e)
    
    def removeService(self):
        """This function ... 
        
        :param param_1: <class> or <type>
        :param param_2: <class> or <type>
        :rtype: <return type>
        """
        
        self.logger.log(logging.INFO, "")
        
        try:
            NodeService.RemoveService()
            self.logger.log(logging.INFO, "Service removed.")
        except Exception as e:
            self.logger.log(logging.ERROR, "Error removing service: %s" % e)